// SPDX-License-Identifier: GPL-2.0

#define pr_fmt(fmt) "DIM Utils: " fmt

#include <linux/fs.h>
#include <linux/err.h>
#include <linux/errno.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <linux/limits.h>
#include <linux/vmalloc.h>
#include <linux/namei.h>
#include <linux/hugetlb_inline.h>
#include "dim_utils.h"

char *dim_path_join(const char *dir, const char *file)
{
	int len, dir_len, file_len, ext;
	char *path = NULL;

	if (dir == NULL || file == NULL)
		return ERR_PTR(-EINVAL);

	dir_len = strlen(dir);
	file_len = strlen(file);
	ext = 1;

	if (dir_len > 0 && dir[dir_len - 1] == '/')
		ext = 0;

	len = dir_len + file_len + ext + 1;
	if (len > PATH_MAX)
		return ERR_PTR(-ENAMETOOLONG);

	path = kmalloc(len + 1, GFP_KERNEL);
	if (path == NULL)
		return ERR_PTR(-ENOMEM);

	snprintf(path, len + 1, "%s%s%s", dir, ext ? "/" : "", file);

	return path;
}

bool dim_filename_has_ext(const char *filename, const char *ext)
{
	int name_len, ext_len;

	if (filename == NULL || ext == NULL)
		return false;

	name_len = strlen(filename);
	ext_len = strlen(ext);
	if (name_len <= ext_len)
		return false;

	return strcmp(filename + name_len - ext_len, ext) == 0 ? true : false;
}

int dim_digest_compare(unsigned char *data1,
		       unsigned char *data2,
		       unsigned int len)
{
	unsigned int i;

	if (data1 == NULL || data2 == NULL)
		return -1;

	for (i = 0; i < len; i++) {
		if (data1[i] == data2[i])
			continue;

		return data1[i] > data2[i] ? 1 : -1;
	}

	return 0;
}

#define INVALID_FLAG	127

static unsigned char char_to_num(char c)
{
	unsigned char base = 10;

	if (c >= '0' && c <= '9')
		return c - '0';
	else if (c >= 'a' && c <= 'f')
		return c - 'a' + base;
	else if (c >= 'A' && c <= 'F')
		return c - 'A' + base;

	return INVALID_FLAG;
}

int dim_string_to_hex(const char *str,
		      int str_len,
		      unsigned char *buf,
		      int buf_len)
{
	int i;
	unsigned char high, low;

	if (((str_len & 0x01) != 0) || ((str_len >> 1) > buf_len))
		return -1;

	for (i = 0; i < buf_len; i++) {
		high = char_to_num(str[i << 1]);
		if (high > 16)
			return -1;

		low = char_to_num(str[(i << 1) + 1]);
		if (low > 16)
			return -1;

		buf[i] = (high << 4) | low;
	}

	return 0;
}

int dim_hex_to_string(unsigned char *data,
		      int data_len,
		      char *buf,
		      int buf_len)
{
	int i;

	if (((data_len << 1) + 1) > buf_len)
		return -1;

	for (i = 0; i < data_len; i++)
		sprintf(&buf[i * 2], "%02x", data[i]);

	buf[i * 2] = '\0';
	return 0;
}

char *dim_get_absolute_path(const char *name, char *buf, int len)
{
	int ret;
	struct path path;
	char *result = NULL;

	if (buf == NULL || name == NULL)
		return ERR_PTR(-EINVAL);

	ret = kern_path(name, LOOKUP_FOLLOW, &path);
	if (ret < 0)
		return ERR_PTR(ret);

	result = d_path(&path, buf, len);
	path_put(&path);
	return result;
}

struct file *dim_get_vma_file(struct vm_area_struct *vma)
{
	if (vma == NULL)
		return NULL;

#ifdef CONFIG_EULEROS_EXEC_HUGEPAGES
	if (exec_hugepages_enabled && is_vm_exec_hugepages(vma) && vma->vm_real_file != NULL)
		return vma->vm_real_file;
#endif
	return vma->vm_file;
}

